/* Copyright (c) 2010-2020 Vadim Surov.
 */

package com.srv.server;

import java.util.ArrayList;
import java.util.List;

import javax.jdo.JDOHelper;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import javax.jdo.annotations.Element;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.memcache.MemcacheServiceFactory;

public class Store {

	private final PersistenceManagerFactory factory;

	/**
	 * Create a new Store based on a particular config.
	 *
	 * @param config
	 */
	public Store(String config) {
		this.factory = JDOHelper.getPersistenceManagerFactory(config);
	}

	/**
	 * Starts a data store session and returns an Api object to use.
	 *
	 * @return
	 */
	public Api getApi() {
		return new Api();
	}

	/**
	 * A reference to a cache service.
	 */
	private final Cache cache = new Cache(MemcacheServiceFactory
			.getMemcacheService());	


	public class Api {

		/**
		 * The JDO persistence manager used for all calls.
		 */
		private final PersistenceManager manager;

		private Api() {
			manager = factory.getPersistenceManager();
		}

		/**
		 * Begin a new transaction.
		 *
		 * @return the transaction
		 */
		public Transaction begin() {
			final Transaction tx = manager.currentTransaction();
			tx.begin();
			return tx;
		}

		/**
		 * Close the connection to the data store. Clients are expected to guarantee
		 * that close will be called. This will also rollback any active
		 * transaction.
		 */
		public void close() {
			final Transaction tx = manager.currentTransaction();
			if (tx.isActive()) {
				tx.rollback();
			}
			manager.close();
		}

		/**
		 * Gets the author by email.
		 *
		 * @param email
		 *          the author's email
		 * @return the author
		 * @throws JDOObjectNotFoundException
		 */
		public Author getAuthor(String email) {
			return manager.getObjectById(Author.class, email);
		}		

		/**
		 * Persist an author to the data store.
		 *
		 * @param author
		 *          the author to be persisted
		 * @return <code>author</code>, for call chaining
		 */
		public Author saveAuthor(Author author) {
			return manager.makePersistent(author);
		}

		/**
		 * Delete an author from the data store.
		 *
		 * @param author
		 *          the author to be deleted
		 * @return void
		 */
		public void deleteAuthor(Author author) {
			manager.deletePersistent(author);
		}

		/**
		 * Looks in the data store for an author with a matching email. If the
		 * author does not exist, a new one will be created. The newly created
		 * author will also have access to a newly created set.
		 *
		 * @param user
		 *          the user for which an author object is needed
		 * @return an author object
		 */

		public Author getOrCreateNewAuthor(String name, String email) {

			Author author = null;
			try {
				// Cache lookup failed, query the data store.
				author = getAuthor(email);
			} catch (JDOObjectNotFoundException e) {
				// If an author wasn't found, we create a new one and save it to the
				// store.

				author = new Author(name, email);
				final Transaction tx = begin();
				saveAuthor(author);
				tx.commit();
			}

			return author;
		}		

	}


	/**
	 * An ORM object representing an author.
	 */
	@PersistenceCapable(identityType = IdentityType.APPLICATION)
	public static class Author {
		/**
		 * The author's email. Serves as the primary key for this object.
		 */
		@PrimaryKey
		private String email;

		/**
		 * The author's name.
		 */
		@Persistent
		private String name;

		/**
		 * The keys of all sets this author has access to.
		 */
		@Persistent(defaultFetchGroup = "true")
		@Element(dependent = "true")
		private List<String> setInfoKeys;

		/**
		 * 
		 */
		@Element(dependent = "true")
		private List<String> blobIds;

		/**
		 * 
		 */
		@Element(dependent = "true")
		private List<String> blobKeys;


		/**
		 * Construct a new author.
		 *
		 * @param email
		 *          the author's email
		 * @param name
		 *          the author's name
		 */
		public Author(String name, String email) {
			setName(name);
			setEmail(email);

			setInfoKeys = new ArrayList<String>();
			blobIds = new ArrayList<String>();
			blobKeys = new ArrayList<String>();
		}

		/**
		 * Give this author access to a set.
		 *
		 * @param set
		 *          the set the author is being granted access to.
		 */
		public void addSetInfoKey(String key) {
			setInfoKeys.add(0, key);
		}

		public void removeSetInfoKey(String setInfoKey) {
			setInfoKeys.remove(setInfoKey);
		}

		/**
		 * Gets the author's email.
		 *
		 * @return
		 */
		public String getEmail() {
			return email;
		}

		/**
		 * Gets the author's name.
		 *
		 * @return
		 */
		public String getName() {
			return name;
		}

		/**
		 * Returns the keys for each set that the author has access to.
		 *
		 * @return
		 */
		public List<String> getSetInfoKeys() {
			return setInfoKeys;
		}

		/**
		 * Returns whether this author has access to a particular set.
		 *
		 * @param setKey
		 *          the key of the set
		 * @return <code>true</code> if the author does have access,
		 *         <code>false</code> otherwise
		 */
		public boolean hasSetInfo(Key setKey) {
			return setInfoKeys.contains(setKey);
		}

		/**
		 * Sets the author's email.
		 *
		 * @param email
		 */
		public void setEmail(String email) {
			this.email = email;
		}

		/**
		 * Sets the author's name.
		 *
		 * @param name
		 */
		public void setName(String name) {
			this.name = name;
		}

		/**
		 * Reassigns the collection of set keys. This is required to ensure that
		 * the ORM will persist element collections.
		 *
		 * @param keys
		 */
		public void setSetInfoKeys(List<String> keys) {
			setInfoKeys = keys;
		}

		public boolean isSetInfoListEmpty() {
			return setInfoKeys.isEmpty();
		}

		public void setBlobKey(String blobId, String blobKey) {
			blobIds.add(blobId);
			blobKeys.add(blobKey);
		}

		public List<String> getBlobIds() {
			return blobIds;
		}

		public List<String> getBlobKeys() {
			return blobKeys;
		}		

	}

}
